#include "Human.h"
#include "World.h"
#include "WorldInfoController.h"
#include "SharedConstants.h"
#include "ctime"

namespace Directions
  {
  const size_t NUMBER_OF_DIRECTIONS = 4;

  const TVector3 NORTH  = TVector3( 1, 0, 0);
  const TVector3 SOUTH  = TVector3(-1, 0, 0);

  const TVector3 WEST  = TVector3(0, -1, 0);
  const TVector3 EAST  = TVector3(0,  1, 0);

  const TVector3 ZERO   = TVector3(0, 0, 0);

  /* Possible directions, clockwise */
  const TVector3 directions[] = {NORTH, EAST, SOUTH, WEST};
  }



Human::Human(World& i_world, EHumanTypes i_type, TVector3 i_position,
             IWorldInfoController& i_world_info_controller, boost::optional<EBalloonColor> i_color)
  : m_world(i_world)
  , m_world_info_controller(i_world_info_controller)
  , m_direction(Directions::ZERO)
  {
  switch (i_type)
    {
    case EHumanTypes::HT_DEFAULT_HUMAN:
      m_type = "DEFAULT_HUMAN_0";
      m_name = m_type + " " + std::to_string(m_id_cnt++);
      m_state = 0;
      break;
    default:
      break;
    }
  SetPosition(i_position);
  _ChangeDirection();

  if(i_color)
    {
    mp_balloon.reset(new Balloon(m_world, i_color.get(), HumanConstants::GIRL_BALLOON_POSITION, this));
    }

  m_world.AddRenderable(this);
  m_world.AddAnimated(this);
  m_world.AddMovable(this);
  }

void Human::Move()
  {
  const double next_x = m_position.X() + _GetStep().X();
  const double next_y = m_position.Y() + _GetStep().Y();
  const double next_z = m_world_info_controller.GetHeightForXY(TVector2(next_x, next_y)) * GroundConstants::CELL_SIZE;
  const TVector3 next_position = TVector3(next_x, next_y, next_z);
  _Move(next_position);

  if(!mp_balloon && m_world_info_controller.CanHumanAcquireBalloon(m_position))
    {
    mp_balloon.reset(new Balloon(m_world, static_cast<EBalloonColor>(rand() % 2),
                                 HumanConstants::GIRL_BALLOON_POSITION, this));
    }
  }

void Human::_Move(TVector3 i_position)
  {
  SetPosition(i_position);
  _ChangeDirection();
  }

void Human::_ChangeDirection()
  {  
  const bool continue_movement = m_world_info_controller.AvailableForWalking(this->GetName(), TVector2(m_position.X() + m_direction.X() * HumanConstants::WALKING_BOUNDARY,
    m_position.Y() + m_direction.Y() * HumanConstants::WALKING_BOUNDARY));

  if(m_direction != Directions::ZERO &&  continue_movement)
    {
    return;
    }

  m_direction = Directions::ZERO;

  auto dice = rand() % Directions::NUMBER_OF_DIRECTIONS;
  const TVector3 choices[] = {Directions::directions[dice],  /* Random direction */
                              Directions::directions[(dice + 1) % Directions::NUMBER_OF_DIRECTIONS],   /* Turn left from random direction */
                              Directions::directions[(dice + 3) % Directions::NUMBER_OF_DIRECTIONS],   /* Turn right from random direction */
                              Directions::directions[(dice + 2) % Directions::NUMBER_OF_DIRECTIONS]};  /* Turn back from random direction */

  for (TVector3 dir : choices)
    {
    if (m_world_info_controller.AvailableForWalking(this->GetName(), TVector2(m_position.X() + dir.X() * HumanConstants::WALKING_BOUNDARY,
                                                    m_position.Y() + dir.Y() * HumanConstants::WALKING_BOUNDARY)))
      {
      m_direction = dir;
      return;
      }
    }
  }

TVector3 Human::_GetStep() const
  {
  return m_direction * HumanConstants::STEP_LENGTH;
  }

double Human::GetAngleAroundY() const 
  {
  /*
  * Considering Directions::EAST being initial direction
  * TODO: initial direction field
  */
  if (Directions::SOUTH == m_direction)
    return 270.0;
  if (Directions::WEST == m_direction)
    return 180.0;
  if (Directions::NORTH == m_direction)
    return 90.0;

  return 0.0;
  }

void Human::NextFrame()
  {
  switch (m_state)
    {
    case 0:
      m_state = 1;
      m_type = "DEFAULT_HUMAN_1";
      break;
    case 1:
      m_state = 0;
      m_type = "DEFAULT_HUMAN_0";
      break;
    default:
      break;
    }
  }
void Human::SetPosition(double i_coord_x, double i_coord_y, double i_coord_z) 
  {
  __super::SetPosition(i_coord_x, i_coord_y, i_coord_z);
  m_world_info_controller.SetHumanPosition(this->GetName(), this->GetPosition());
  }
void Human::SetPosition(const TVector3& i_position) 
  {
  __super::SetPosition(i_position);
  m_world_info_controller.SetHumanPosition(this->GetName(), this->GetPosition());
  }
std::string Human::GetName() const
	{
	return m_name;
	}

const Balloon* Human::GetBalloon() const
  {
  return mp_balloon.get();
  }

Human::~Human()
  {
  m_world.RemoveRenderable(this);
  m_world.RemoveAnimated(this);
  m_world.RemoveMovable(this);
  }